/**
 * @author msi
 * 双重校验锁：面试通常会将 instance的 volatile 去掉，并问为什么需要加上这个关键字。
 */
public class SingleInstance {

    /**
     * 使用 volatile 可以禁止new 对象时进行指令重排序。
     *  new 对象，实际上有三条指令完成。
     *      1.JVM为对象分配一块内存M在堆区。
     *      2.在内存M上为对象进行初始化。
     *      3.将内存M的地址赋值给instance变量
     *  其中这三步是可能发生指令重排序的，原来 1 - 2 - 3 可以变成 1 - 3 - 2.
     *  如果是1 - 3 - 2 的情况，可能会发生别的线程获取到一个半成品对象的可能，（因为instance先赋值，别的对象拿到这个地址就认为已经有一个单例对象了，
     *          但是这时候还没有对这个对象进行初始化，是一个半成品。）
     *  所以使用volatile修饰，禁止指令重排序。
     *  有个问题：synchronized也是可以实现有序性的啊。为什么还会出现这样的问题呢？
     *      这其实是一个迷惑点。
     *          sync同步代码块的有序性：由于这是一个同步代码块，所以它只能保证，代码块之前的先执行，之后的后执行，但是对于代码块中的重排序，它没有办法。
     *          volatile的有序性：volatile就不同了，它是修饰变量的，它能保证，instance之前的先执行，instance之后的后执行，所以在第三步将
     *              内存M的地址赋值给instance变量，就是在最后一步执行的。不会出现 1 - 3 - 2 的情况。
     */
    private volatile static SingleInstance instance;

    private SingleInstance(){
        //这一步是一个小优化，为了放置某些操作使用反射创建对象。
        if(instance!=null){
            throw new RuntimeException("single instance existed");
        }
    }

    public SingleInstance getInstance(){
        if(instance == null){

            synchronized (SingleInstance.class){
                if(instance == null){
                    //为什么instance需要加上volatile修饰
                    instance=new SingleInstance();
                }
            }
        }
        return instance;
    }
}
